package com.meida.paotui.bus.provider.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author:yexg
 * @Date:2019-11-07 11:50
 * @Description:  redis 操作地理位置
 **/
@Component
@Slf4j
public class RedisGeoUtils {
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * @MethodName：cacheGeo
     * @param k key
     * @param x 经度
     * @param y 维度
     * @param member  用户
     * @param time(单位秒)  <=0 不过期
     * @return
     * @ReturnType：boolean
     * @Description：缓存地理位置信息
     */
    private final String KEY_PREFIX_GEO = "prefix_geo_";

    public boolean addGeo(String k, double x, double y, String member, long time) {
        String key = KEY_PREFIX_GEO + k;
        try {
            GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
            geoOps.add(key, new Point(x, y), member);
            if (time > 0) redisTemplate.expire(key, time, TimeUnit.MILLISECONDS);
        } catch (Throwable t) {
            t.printStackTrace();
            log.error("缓存[" + key + "]" + "失败, point[" + x + "," +
                y + "], member[" + member + "]" + ", error[" + t + "]");
            return false;
        }
        return true;
    }

    /**
     * @param k         key
     * @param locations 位置信息
     * @param time(单位秒) <=0 不过期
     * @return
     * @MethodName：cacheGeo
     * @ReturnType：boolean
     * @Description：缓存地理位置信息
     */
    public boolean addAllGeo(String k, Iterable<GeoLocation<String>> locations, long time) {
        String key = KEY_PREFIX_GEO + k;
        try {
            for (GeoLocation<String> location : locations) {
                addGeo(k, location.getPoint().getX(), location.getPoint().getY(), location.getName(), time);
            }
        } catch (Throwable t) {
            log.error("缓存[" + KEY_PREFIX_GEO + k + "]" + "失败" + ", error[" + t + "]");
            return false;
        }
        return true;
    }

    /**
     * @param k       key
     * @param members 用户
     * @return
     * @MethodName：removeGeo
     * @ReturnType：boolean
     * @Description：移除地理位置信息
     */
    public boolean removeGeo(String k, String... members) {
        String key = KEY_PREFIX_GEO + k;
        try {
            GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
            geoOps.remove(key, members);
        } catch (Throwable t) {
            log.error("移除[" + key + "]" + "失败" + ", error[" + t + "]");
            return false;
        }
        return true;
    }

    /**
     * @param k       key
     * @param member1 成员1
     * @param member2 成员2
     * @return Distance
     * @MethodName：distanceGeo
     * @ReturnType：Distance
     * @Description：根据两个成员计算两个成员之间距离
     */
    public Distance distanceGeo(String k, String member1, String member2) {
        String key = KEY_PREFIX_GEO + k;
        try {
            GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
            return geoOps.distance(key, member1, member2);
        } catch (Throwable t) {
            log.error("计算距离[" + key + "]" + "失败, member[" + member1 + "," + member2 + "], error[" + t + "]");
        }
        return null;
    }

    /**
     * @param k       key
     * @param members 成员
     * @return
     * @MethodName：getGeo
     * @ReturnType：List<Point>
     * @Description：根据key和member获取这些member的坐标信息
     */
    public List<Point> getGeo(String k, String... members) {
        String key = KEY_PREFIX_GEO + k;
        try {
            GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
            return geoOps.position(key, members);
        } catch (Throwable t) {
            log.error("获取坐标[" + key + "]" + "失败]" + ", error[" + t + "]");
        }
        return null;
    }

    /**
     * @param key
     * @param x         经度
     * @param y         维度
     * @param distance  km
     * @param direction 排序方式
     * @param limit 数量
     * @return
     * @MethodName：radiusGeo
     * @ReturnType：GeoResults<GeoLocation<String>>
     * @Description：通过给定的坐标和距离(km)获取范围类其它的坐标信息
     */
    public GeoResults<GeoLocation<String>> radiusGeo(String key, double x, double y,
                                                     double distance,
                                                     Sort.Direction direction, long limit) {
        try {
            String k = KEY_PREFIX_GEO + key;
            GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
            //设置geo查询参数
            GeoRadiusCommandArgs args = GeoRadiusCommandArgs.newGeoRadiusArgs().
             includeCoordinates();
            if (Sort.Direction.ASC.equals(direction)) {//按查询出的坐标距离中心坐标的距离进行排序
                args.sortAscending();
            } else if (Sort.Direction.DESC.equals(direction)) {
                args.sortDescending();
            }
            args.limit(limit);//限制查询数量
            GeoResults<GeoLocation<String>> radiusGeo = geoOps.radius
                (k, new Circle(new Point(x, y), new Distance(distance, DistanceUnit.KILOMETERS)), args);
            return radiusGeo;
        } catch (Throwable t) {
            log.error("通过坐标[" + x + "," + y + "]获取范围[" + distance + "km的其它坐标失败]" + ", error[" + t + "]");
        }
        return null;
    }
}
